package com.nx.platform.es.biz.query.search;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.nx.platform.es.entity.response.SearchResult;
import org.apache.lucene.search.Explanation;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.search.SearchHit;

import java.util.*;

/**
 * @author
 * @since 2016年10月15日
 */
public class SearchResponses {

    private SearchRequestContext context;
    private SearchResponse response;

    private SearchResponses() {}

    public static SearchResponses create(SearchRequestContext context, SearchResponse response) {
        SearchResponses r = new SearchResponses();
        r.response = response;
        r.context = context;
        return r;
    }

    public <T> T to(SearchResponseConverter<T> converter, T defaultValue) {
        if (response != null) {
            return converter.convert(context, response);
        } else {
            return defaultValue;
        }
    }

    public SearchResult toSearchResult() {
        return to(SearchResultConverter.INSTANCE, new SearchResult());
    }

    public enum SearchResultConverter implements SearchResponseConverter<SearchResult> {
        INSTANCE;

        @Override
        public SearchResult convert(SearchRequestContext context, SearchResponse response) {
            Preconditions.checkNotNull(response, "response is null");
            //
            int totalHits = (int) response.getHits().getTotalHits();
            SearchHit[] hits = response.getHits().getHits();
            //
            LinkedHashMap<String, Map<String, Object>> hitsMap;
            hitsMap = new LinkedHashMap<>(hits.length);
            //
            String[] fetch = context.getFetch();
            if (fetch == null) {
                fetch = new String[0];
            }
            // int size = fetch.length + 2;
            int size = fetch.length + 1;
            for (SearchHit hit : hits) {
                // 如果不需要返回附加信息(直接添加空Map)
                Map<String, Object> source;
                // 需要返回附加信息
                source = new HashMap<>(size);
                float score = hit.getScore();
                if (!Float.isNaN(score) && score != 0.0) {
                    source.put("_score", score);
                }

                if (context.isExplain()) {
                    SearchExplainNode searchExplainNode = parseExplain(hit.getExplanation(), 0);
                    source.put("_explain", String.valueOf(searchExplainNode));
                }
                // Object[] _sort = hit.getSortValues();
                // if (_sort != null && _sort.length > 0) {
                // source.put("_sort", hit.getSortValues());
                // }
                for (String f : fetch) {
                    DocumentField field = hit.getFields().get(f);
                    if (field != null) {
                        List<?> values = field.getValues();
                        if (values != null && !values.isEmpty()) {
                            source.put(field.getName(), Joiner.on(',').skipNulls().join(values));
                        }
                    }
                }
                hitsMap.put(hit.getId(), source);
            }
            return new SearchResult(totalHits, hitsMap);
        }

        private SearchExplainNode parseExplain(Explanation explanation, Integer level) {
            if (explanation != null) {
                SearchExplainNode node = new SearchExplainNode();
                // 此时必然会有值
                node.setValue(explanation.getValue());
                node.setDescription(explanation.getDescription());
                node.setLevel(level);

                List<SearchExplainNode> searchExplainNodes = new ArrayList<>();

                Integer newLevel = level + 1;

                for (Explanation explanation1 : explanation.getDetails()) {
                    searchExplainNodes.add(parseExplain(explanation1, newLevel));
                }
                node.setDetails(searchExplainNodes);

                return node;
            }

            return null;
        }
    }

    public interface SearchResponseConverter<T> {
        T convert(SearchRequestContext context, SearchResponse response);
    }

}
